package com.cc.flyingfish.util;

import com.github.tobato.fastdfs.domain.fdfs.FileInfo;
import com.github.tobato.fastdfs.domain.fdfs.MetaData;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.service.AppendFileStorageClient;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;

/**
 * @author CHENCHEN
 * @title
 * @createTime 2023-03-11 11:54
 */
@Slf4j
@Component
public class FastDFSUtil {

    @Autowired
    private FastFileStorageClient fastFileStorageClient;

    @Autowired
    private AppendFileStorageClient appendFileStorageClient;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    public static final String DEFAULT_GROUP = "group1";

    // PATH_KEY + fileMd5 判断文件 名 是否上传过
    public static final String PATH_KEY = "path-key";

    // UPLOADED_SIZI_KEY + fileMd5 判断文件 大小 是否上传过
    public static final String UPLOADED_SIZI_KEY = "uploaded-sizi-key";

    // UPLOADED_NO_KEY + fileMd5 判断文件 总片 是否上传过
    public static final String UPLOADED_NO_KEY = "uploaded-no-key";

    //默认一个分片大小 2M
    public static final int SLICE_SIZE = 1024 * 1024 * 2;
    /**
     * 获取文件类型
     * @param file
     * @return
     */
    public String getFileType(MultipartFile file){
        if(file == null){
            //抛出异常，非法文件
        }
        //获取文件名称
        String filename = file.getOriginalFilename();
        //获取最后一个 .下标位置
        int index = filename.lastIndexOf(".");
        //截取文件后缀名
        return filename.substring(index+1);
    }

    //可以断点续传的方法
    public String uploadAppendFile(MultipartFile file) throws Exception {
        String fileType = this.getFileType(file);
        String filename = file.getOriginalFilename();
        StorePath storePath = appendFileStorageClient.uploadAppenderFile(DEFAULT_GROUP, file.getInputStream(), file.getSize(), fileType);
        return storePath.getPath();
    }

    //针对断点进行覆盖续传
    public void modifyAppendFile(MultipartFile file, String filePath, long offset) throws IOException {
        appendFileStorageClient.modifyFile(DEFAULT_GROUP,filePath,file.getInputStream(),file.getSize(),offset);
    }

    public String uploadFileBySlices(MultipartFile file, String fileMd5, Integer sliceNo, Integer totalSliceNo) throws Exception {
        if(file == null || sliceNo == null || totalSliceNo == null){
            //参数异常
        }
        String pathKey = PATH_KEY + fileMd5;
        String uploadedSizeKey = UPLOADED_SIZI_KEY + fileMd5;
        String uploadedNoKey = UPLOADED_NO_KEY + fileMd5;
        //先判断当前上传进度
        String uploadedSizeKeyStr = redisTemplate.opsForValue().get(uploadedSizeKey);
        //分片起始位置
        long uploadedSize = 0L;
        if(uploadedSizeKeyStr != null){
            //如果Redis中有，获取当前文件大小
            uploadedSize = Long.valueOf(uploadedSizeKeyStr);
        }
        //判断当前是不是第一个分片
        if(sliceNo.equals(1)){
            String path = this.uploadAppendFile(file);
            if (path == null){
                //上传失败
            }
            redisTemplate.opsForValue().set(pathKey, path);
            redisTemplate.opsForValue().set(uploadedNoKey, "1");
        }else{
            String filePath = redisTemplate.opsForValue().get(pathKey);
            if(filePath == null){
                //上传失败
            }
            //修改上传位置文件
            this.modifyAppendFile(file,filePath, uploadedSize);
            //自增分片
            redisTemplate.opsForValue().increment(uploadedNoKey);
        }
        //修改上传大小
        uploadedSize += file.getSize();
        redisTemplate.opsForValue().set(uploadedSizeKey, String.valueOf(uploadedSize));
        //如果所有分片都上传完毕了，清空Redis相关信息
        String resultPath = "";
        if(Integer.valueOf(redisTemplate.opsForValue().get(uploadedNoKey)).equals(totalSliceNo)){
            resultPath = redisTemplate.opsForValue().get(pathKey);
            List<String> list = Arrays.asList(uploadedNoKey, uploadedSizeKey, pathKey);
            redisTemplate.delete(list);
        }
        return resultPath;
    }

    //将 MultipartFile 文件转换成 File 类型
    public File multipartFiltToFile(MultipartFile multipartFile) throws Exception {
        String originalFilename = multipartFile.getOriginalFilename();
        String[] strs = originalFilename.split("\\.");
        File file = File.createTempFile(strs[0],"." + strs[1]);
        multipartFile.transferTo(file);
        return file;
    }

    //后端断点续传
    public void convertFileToSlices(MultipartFile multipartFile) throws Exception {
        //获取文件全名
        String filename = multipartFile.getOriginalFilename();
        //获取最后一个.下标
        int index = filename.lastIndexOf(".");
        //截取到文件名
        String name = filename.substring(0, index);
        //获取文件类型
        String fileType = this.getFileType(multipartFile);
        //转换File类型
        File file = this.multipartFiltToFile(multipartFile);
        //获取文件长度
        long fileLength = file.length();
        //设置分片大小并进行分片设置
        int count = 1;
        for(int i=0;i<fileLength; i+=SLICE_SIZE){
            //随机读取任意一个位置进行分片
            RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
            //搜寻起始位置
            randomAccessFile.seek(i);
            byte[] bytes = new byte[SLICE_SIZE];
            //获取实际读取长度
            int len = randomAccessFile.read(bytes);
            //设置一个临时目录存储文件
            String tmpPath = "E:\\tmpfile\\" + name + "\\"+ count + "." + fileType;
            //创建文件
            File slice = new File(tmpPath);
            File parentFile = slice.getParentFile();

            if (!parentFile.exists()) {
                parentFile.mkdirs();
            }
            //创建输出流
            FileOutputStream fileOutputStream = new FileOutputStream(slice);
            //写出文件到临时目录
            fileOutputStream.write(bytes,0,len);
            randomAccessFile.close();
            count++;
        }
        //删除临时目录
        file.delete();
    }

    public void viewVideoOnlineBySlices(HttpServletRequest request, HttpServletResponse response, String path) throws Exception {
        //从fastdfs中获取文件
        FileInfo fileInfo = fastFileStorageClient.queryFileInfo(DEFAULT_GROUP, path);
        //获取文件长度
        long totalFileSize = fileInfo.getFileSize();
        //资源请求路径
        String url = "http://localhost/group1/" + path;
        //获取所有请求头
        Enumeration<String> headerNames = request.getHeaderNames();
        Map<String, Object> headers = new HashMap<>();
        while (headerNames.hasMoreElements()){
            String header = headerNames.nextElement();
            headers.put(header, request.getHeader(header));
        }
        String rangeStr = request.getHeader("Range");
        String[] range;
        if(StringUtil.isNullOrEmpty(rangeStr)){
            rangeStr = "bytes=0-" + (totalFileSize-1);
        }
        range = rangeStr.split("bytes=|-");
        long begin = 0;
        if(range.length >= 2){
            begin = Long.parseLong(range[1]);
        }
        long end = totalFileSize-1;
        if(range.length >= 3){
            end = Long.parseLong(range[2]);
        }
        long len = (end - begin) + 1;
        String contentRange = "bytes " + begin + "-" + end + "/" + totalFileSize;
        response.setHeader("Content-Range", contentRange);
        response.setHeader("Accept-Ranges", "bytes");
        response.setHeader("Content-Type", "video/mp4");
        response.setContentLength((int)len);
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
        HttpUtil.get(url, headers, response);

    }

    //上传
    public String uoloadFile(MultipartFile file) throws Exception {
        //属性设置
        Set<MetaData> metaData = new HashSet<>();
        //获取文件类型
        String fileType = this.getFileType(file);
        //上传
        StorePath storePath = fastFileStorageClient.uploadFile(file.getInputStream(), file.getSize(), fileType, metaData);
        return storePath.getPath();
    }

    //删除
    public void deleteFile(String filePath){
        fastFileStorageClient.deleteFile(filePath);
    }

    public String getFileMD5(MultipartFile file) throws Exception {
        return getFileMD5Str(file);
    }

    //获取文件md5加密后的字符串
    private static String getFileMD5Str(MultipartFile file) throws Exception {
        InputStream fis = file.getInputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int byteRead;
        while((byteRead = fis.read(buffer)) > 0){
            baos.write(buffer, 0, byteRead);
        }
        fis.close();
        return DigestUtils.md5Hex(baos.toByteArray());
    }
}
